25. Swap(without throw)

swap
swap은 C++에서 STL에 포함된 이래 예외 안전성 프로그램에 없어서 안될 함수이다.
자기대입 현상을 대체하기 위한 대표적인 매커니즘
namespace std{
template <typename T>
void swap(T& a, T& b){
T temp(a); // 1
a=b; // 2
b=temp; // 3
}
}
위는 표준에서 제공하는 표준 swap이다. 이는 3번의 복사 연산을 포함한다.
위와 같은 단순 교환 함수를 포인터가 포함된 클래스에 사용할 경우, 단순히 포인터를 바꾸는 것보다
많은 연산을 수행하게 된다.
(포인터를 포함한 연산자에게 위와 같이 새로운 개체 temp에 복사 생성할 경우, 깊은 복사를 수행하게 된다.)
swap class_pimpl(pointer to implementation)
class WidgetImpl{
public:
// ...
private:
int a, b, c;
std::vector<double> v;
// ...
};
class Widget{
public:
Widget(const Widget& rhs);
Widget& operator=(const Widget& rhs){ //
// ...
*pImpl=*(rhs.pImpl);
// ...
return *this;
}
// ...
private:
WidgetImpl* pImpl;
};
swap 템플릿을 특수화(specialize)하여 내부의 pImpl 포인터에 대하여, 단순 포인터만 교환하도록 선언한다.
namespace std{
template <>
void swap<Widget>(Widget& a, Widget& b){
swap(a.pImpl, b.pImpl); // Widget
// friend
}
}
완전 템플릿 특수화(total template specializaiton) 함수 swap을 Widget에서 friend로 선언해도 되지만,
이보다, Widget내에 swap 멤버함수를 추가하고, 특수화 함수에서 이를 호출하도록 구현할 수 있다.
class Widget{
public:
// ...
void swap(Widget& other){
using std::swap;
swap(pImpl, other.pImpl);
}
// ...
};
namespace std{
template <>
void swap<Widget>(Widget& a, Widget& b){
a.swap(b);
}
}
swap template class
template <typename T>
class WidgetImpl{ /* ... */ };
class Widget{ /* ... */ };
namespace std{
template <typename T>
void swap<Widget<T>>(Widget<T>& a, widget<T>& b){ a.swap(b); } // error
}
C++는 클래스 템플릿에 대해서 부분 특수화(partial specialization)을 허용한다.
하지만, 함수 템플릿의 부분 특수화는 허용하지 않는다.

함수 템플릿을 부분 특수화 하고 싶은 경우,
오버로드를 하나 추가하여 사용해야 한다.
namespace std{
template <typename T>
void swap(Widget<T>& a, Widget<T>& b){
a.swap(b);
} //
}
일반적인 함수 템플릿은 위와 같이 오버로드 해서 사용 가능하다.
하지만, std namespace에 정의되어 있는 템플릿에 대하여서는
특수화는 가능하지만, 새로운 템플릿을 추가(오버로드)는 금지되어 있다.

(위의 코드는 컴파일이 가능하지만, 실행되는 결과가 미정의 사항이다.)
namespace WidgetStuff{
// ...
template <typename T>
class Widget{ /* ... */ };
// ...
template <typename T>
void swap(Widget<T>& a, Widget<T>& b){
a.swap(b);
}
}
따라서 템플릿 함수에 대하여서 오버로드 하고 싶은 경우,
std::swap의 특수화나 오버로드가 아닌
사용하고자 하는 namespace에 새롭게 swap 템플릿 함수를 정의해서 사용하면 된다.

컴파일러는 Widget 객체에 대해 swap이 호출되었을 때, C++의 이름 탐색 규칙
인자 기반 탐색(argument-dependent lookup) 혹은 쾨니그 탐색(Koenig lookup)에 따라
WidgetStuff 네임스페이스 안에서 Widget 특수화 버전을 찾고 이를 사용한다.
사용자 관점
template <typename T>
void doSomthing(T& obj1, T& obj2){
// ...
swap(obj1, obj2);
// ...
}
위에서 구현한 바와 같이 세가지 종류의 swap 함수가 호출될 수 있다.
1. std::swap 일반형
2. std::swap을 특수화한 버전
3. T 타입 전용의 버전(오버로드); namespace가 std가 아니어야 함
template <typename T>
void doSomething(T& obj1, T& obj2){
using std::swap;
// ...
swap(obj1, obj2);
// ...
}
컴파일러는 이름 탐색 규칙을 따라,
우선 전역 유효범위 혹은 타입 T와 동일한 네임스페이스 안에 T전용 swap 함수를 찾는다.(오버로드)
위와 같이 using std::swap을 선언해 주면, 이를 T 전용 swap 함수보다 우선적으로 사용하도록 할 수 있다.

T 템플릿 특수화 되어 있는 swap이 우선되어 사용된다.